# coding: utf-8


import time
import threading
import hashlib
import os
from concurrent import futures
import shutil
from tkinter import Canvas, Tk, StringVar, Label, Button, NW, \
                     GROOVE, scrolledtext, ttk      # Radiobutton, LabelFrame
import tkinter as tk
import tkinter.messagebox as msgbox
from tkinter.filedialog import askdirectory
import _tkinter
import re
import configparser as cfg
# import sys


class Signal:
    pause = False


class MainWin:
    config_file = 'checker.cfg'
    move_option_list = ('1-copy longest', '2-copy and del', '3-del except longest')
    bakup_option_list = ['1-do not bakup', '2-bakup result']

    def __init__(self, mywin):
        self.signal = False
        self.time_step = 0.5    # for progress bar
        self.time = time.time()
        self.run_list = [1, 2, 3, 4, 5, 6]
        self.run_state = ''

        self.init_task()

        self.findfiler = None
        self.findmd5 = None
        self.findgroup = None
        self.findsame = None

        self.result_dict= {1: None, 2: None, 3: None, 4: None}
        self.result1 = None
        self.result2 = None
        self.result3 = None
        self.result4 = None
        self.result5_fail = None

        # mywin = Tk()
        mywin.title('Check Same Files')
        mywin.geometry('950x500+290+100')
        mywin.config(bg='#635363')
        mywin.resizable = (True, True)
        self.mywin = mywin
        self.win_width = 950
        self.win_height = 500

        self.init_pbar(mywin)
        self.init_button(mywin)
        self.inti_entry(mywin)
        self.init_text(mywin)
        mywin.register(self.check_subdir_num)

    def init_task(self):
        self.task = None
        self.move_path = None
        self.check_path = None
        self.move_option = None
        self.bak_result = None
        self.move_num = None
        self.muve_suffix = None

        self._task = tk.StringVar()
        self._check_path = tk.StringVar()
        self._move_path = tk.StringVar()
        self._bak_result = tk.StringVar()
        self._move_subdir_num = tk.IntVar()     # tk.StringVar()
        self._move_option = tk.StringVar()
        self._move_suffix = tk.StringVar()
        # self._move_num = tk.StringVar()       # not used yet

        self.tasker = Tasker(self.config_file, self.disp_msg)
        self.task_list = [task['name'] for task in self.tasker.task_dict]

        task_recent = self.tasker.get_recent()
        self.set_task(task_recent)

    def set_task(self, task):
        self.task = task['name']
        self.check_path = task['path']
        self.bak_result = task['bakresult']
        self.move_path = task['move_path']
        self.move_subdir_num = task['move_subdir_num']
        self.move_suffix = task['move_suffix']
        move_option = {'1': 0, '2': 1, '3': 2}
        _option = move_option[task['move_option'][0]]
        self.move_option = self.move_option_list[_option]
        self.move_num = task['move_num']         # move group number to avoid to use too much time at one time
        # init entry value
        self._task.set(self.task)
        self._check_path.set(self.check_path)
        self._move_path.set(self.move_path)
        self._bak_result.set(self.bakup_option_list[1] if self.bak_result else self.bakup_option_list[0])
        self._move_option.set(self.move_option)
        self._move_suffix.set(self.move_suffix)
        self._move_subdir_num.set(self.move_subdir_num)

    def task_refresh(self, event):
        _name = self._task.get()
        # save current to self.tasker
        self.tasker.add_task({'name': self.task,
                              'path': self._check_path.get(),
                              'bakresult': self._bak_result.get(),
                              'move_option': self._move_option.get(),
                              'move_path': self._move_path.get(),
                              'move_suffix': self._move_suffix.get(),
                              'move_subdir_num': self._move_subdir_num.get(),
                              'move_num': -1
                              })
        # refresh
        task = self.tasker.get_task(_name)
        self.set_task(task)

    def task_save(self):
        if len(self._task.get().strip()) == 0:
            return
        if self.task != self._task.get():
            self.tasker.add_task({'name': self._task.get(),
                                  'path': self._check_path.get(),
                                  'bakresult': self._bak_result.get(),
                                  'move_option': self._move_option.get(),
                                  'move_path': self._move_path.get(),
                                  'move_suffix': self._move_suffix.get(),
                                  'move_subdir_num': self._move_subdir_num.get(),
                                  'move_num': -1
                                  })
            self.task_list.append(self._task.get())
            self.entry_task['values'] = self.task_list

    # init interface
    def init_pbar(self, mywin):
        # 进度条
        self.bar_length = self.win_width - 10
        self.canvas_progress_bar = Canvas(mywin, width=self.bar_length, height=20)
        self.canvas_shape = self.canvas_progress_bar.create_rectangle(0, 0, 0, 25, fill='green')
        self.canvas_text = self.canvas_progress_bar.create_text(292, 4, anchor=NW)
        self.canvas_progress_bar.itemconfig(self.canvas_text, text='00:00:00')

        self.var_progress_bar_percent = StringVar()
        self.var_progress_bar_percent.set('00.00 %')
        label_progress_bar_percent = Label(mywin,
                                           textvariable=self.var_progress_bar_percent,
                                           fg='#F5F5F5', bg='#535353')

        self.canvas_progress_bar.place(x=5, y=self.win_height-30, width=self.bar_length)
        label_progress_bar_percent.place(x=5, y=self.win_height - 60)

    def init_button(self, mywin):
        # 按钮-1
        button1_start = Button(mywin, text='1: find', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=1),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button1_start.place(relx=0.45, rely=0.5, anchor=tk.SE)

        # 按钮-2
        button2_start = Button(mywin, text='2: md5', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=2),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button2_start.place(relx=0.45, rely=0.5, anchor=tk.SW)

        # 按钮-3
        button3_start = Button(mywin, text='3: group', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=3),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button3_start.place(relx=0.45, rely=0.5, anchor=tk.NE)

        # 按钮-4
        button4_start = Button(mywin, text='4: result', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=4),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button4_start.place(relx=0.45, rely=0.5, anchor=tk.NW)

        # 按钮-5 move
        button5_start = Button(mywin, text='5: move', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=5),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button5_start.place(relx=0.45, rely=0.5, anchor=tk.NW)

        # 按钮-6 bakup
        button6_start = Button(mywin, text=' bakup ', fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=6),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5', activeforeground='#535353')
        button6_start.place(relx=0.45, rely=0.5, anchor=tk.NW)

        # 按钮-7 exit
        button7_start = Button(mywin, text=' exit ', fg='#F5F5F5', bg='#7A7A7A',
                               command=lambda: self.run_button(step=-1),
                               height=1, width=15, relief=GROOVE, bd=2,
                               activebackground='#F5F5F5',
                               activeforeground='#535353')
        button7_start.place(relx=0.45, rely=0.5, anchor=tk.NW)

        # 按钮-8 pause
        self.button8_pause = Button(mywin, text='pause',
                                    fg='#F5F5F5', bg='#7A7A7A',
                                    command=lambda: self.run_button(step=-2),
                                    height=1, width=15, relief=GROOVE, bd=2,
                                    activebackground='#F5F5F5',
                                    activeforeground='#535353')
        self.button8_pause.place(x=self.win_width-80, y=self.win_height-65, width=60)

        button1_start.grid(row=2, column=1, padx=10, pady=10)
        button2_start.grid(row=2, column=2, padx=10, pady=10)
        button3_start.grid(row=2, column=3, padx=10, pady=10)
        button4_start.grid(row=2, column=4, padx=10, pady=10)
        button5_start.grid(row=2, column=5, padx=10, pady=10)
        button6_start.grid(row=2, column=6, padx=10, pady=10)
        button7_start.grid(row=2, column=7, padx=10, pady=10)

    def inti_entry(self, mywin):
        # task
        label_task = tk.Label(mywin, text='task', bg='#535363', fg='#F5F5F5')
        self.entry_task = ttk.Combobox(mywin, width=20,
                                       values=self.task_list,
                                       textvariable=self._task,
                                       postcommand=self.task_save
                                       )
        self.entry_task.bind('<<ComboboxSelected>>', self.task_refresh)

        # check-path
        label_path = tk.Label(mywin, text='path', bg='#535363', fg='#F5F5F5')
        entry_path = tk.Entry(mywin, bd=5, textvariable=self._check_path)
        # entry_path.insert(tk.INSERT, self.check_path)
        button_path = Button(mywin, text='...', fg='#F5F5F5', bg='#7A7A7A',
                             command=self.run_getdir,
                             height=1, width=5, relief=GROOVE, bd=2,
                             activebackground='#F5F5F5', activeforeground='#535353')

        # move-path
        label_path_move = tk.Label(mywin, text='move-path', bg='#535363', fg='#F5F5F5')
        entry_path_move = tk.Entry(mywin, bd=5, textvariable=self._move_path)
        # entry_path_move.insert(tk.INSERT, self.move_path)

        # bak-option
        label_bak = tk.Label(mywin, text='bakresult', bg='#535363', fg='#F5F5F5')
        bak_coms = ttk.Combobox(mywin, width=15,
                                values=self.bakup_option_list,
                                textvariable=self._bak_result)
        # bak_coms.current(0)

        # move-option
        label_move_option = tk.Label(mywin, text='move option', bg='#535363', fg='#F5F5F5')
        self.coms_move_option = ttk.Combobox(mywin, width=15,
                                             values=self.move_option_list,
                                             textvariable=self._move_option
                                             )
        # self.coms_move_option.current(0)

        # move-suffix
        label_suffix_move = tk.Label(mywin, text='move-suffix', bg='#535363', fg='#F5F5F5')
        self.entry_suffix_move = tk.Entry(mywin, bd=5, textvariable=self._move_suffix)
        # self.entry_suffix_move.insert(tk.INSERT, self.move_suffix)

        # subdir-max-num
        label_subdir_num = tk.Label(mywin, text='subdir-file-num', bg='#535363', fg='#F5F5F5')
        self.entry_subdir_num = tk.Entry(mywin, bd=5,
                                         validate='focusout', validatecommand=(self.check_subdir_num, '%P'),
                                         textvariable=self._move_subdir_num)
        # self.entry_subdir_num.insert(tk.INSERT, self.move_subdir_num)

        # place
        left_x = 15
        width = 110
        label_task.place(x=left_x, y=43)
        self.entry_task.place(x=left_x, y=65, width=width)
        left_x += width + 15
        width = 155
        label_path.place(x=left_x, y=43)
        entry_path.place(x=left_x, y=65, width=width)
        left_x += width + 2
        width = 12
        button_path.place(x=left_x, y=65, width=width)
        left_x += width + 15
        width = 110
        label_bak.place(x=left_x, y=45)
        bak_coms.place(x=left_x, y=68, width=width)
        left_x += width + 15
        width = 110
        label_move_option.place(x=left_x, y=45)
        self.coms_move_option.place(x=left_x, y=68, width=width)
        left_x += width + 15
        width = 120
        label_path_move.place(x=left_x, y=43)
        entry_path_move.place(x=left_x, y=65, width=width)
        left_x += width + 15
        width = 80
        label_suffix_move.place(x=left_x, y=43)
        self.entry_suffix_move.place(x=left_x, y=65, width=width)
        left_x += width + 15
        width = 80
        label_subdir_num.place(x=left_x, y=43)
        self.entry_subdir_num.place(x=left_x, y=65, width=width)

    def init_text(self, mywin):
        # result text
        self.text_result = tk.scrolledtext.ScrolledText(mywin, width=129, height=25)
        self.text_result.place(x=12, y=105)
        self.disp_msg(Document.help_doc)

    def check_subdir_num(self, content):
        # check
        # subdir_num = self._move_subdir_num.get()
        if content.isdigit():
            # self.move_subdir_num = int(subdir_num)
            return True
        msgbox.showwarning('subdir max files num set error!',
                           'not valid int digit!')
        # self.move_subdir_num = 1000
        # self._move_subdir_num.set('1000')
        # self.run_state = ''
        return False

    # run functions
    def run_button(self, step=1):

        # acquire value in entries
        self.task = self._task.get()
        self.check_path = self._check_path.get()
        self.bak_result = True if self._bak_result.get()[0] == '2' else False
        self.move_option = self._move_option.get()
        self.move_suffix = self._move_suffix.get()
        self.move_path = self._move_path.get()
        try:
            # subdir_num = int(subdir_num)
            subdir_num = self._move_subdir_num.get()
        except _tkinter.TclError:
            self.disp_msg('input value invalid, must be digits!')
            return
        self.tasker.add_task({'name': self.task,
                              'path': self.check_path,
                              'bakresult': self.bak_result,
                              'move_option': self.move_option,
                              'move_suffix': self.move_suffix,
                              'move_path': self.move_path,
                              'move_subdir_num': subdir_num,
                              'move_num': -1})

        # only button-pause permitted if running
        if len(self.run_state) > 0:
            if step != -2:
                msgbox.showwarning('Running Busy Warning',
                                   '{} is running'.format(self.run_state))
                return

        if step in [1, 2, 3, 4, 5, 6]:
            # set parameters
            self.run_state = 'step-{}'.format(step)
            # clear board
            self.text_result.delete('1.0', 'end')
            target_list = [self.run_prog1, self.run_prog2, self.run_prog3,
                           self.run_prog4, self.run_prog5, self.run_prog6]
            th = threading.Thread(target=target_list[step-1])
            th.setDaemon(True)
            th.start()

        if step == -1:
            self.tasker.set_recent(self.task)
            self.tasker.save(self.config_file)
            self.mywin.destroy()
            return
        if step == -2:
            # no change if idle
            if self.run_state == '':
                return
            # change pause state
            if Signal.pause:
                self.button8_pause['bg'] = '#7A7A7A'
                Signal.pause = False
            else:
                self.button8_pause['bg'] = 'yellow'
                Signal.pause = True
                self.disp_msg('system is suspended!')
            return

    # find subpath and files
    def run_prog1(self):
        self.check_path = self._check_path.get()
        if not os.path.isdir(self.check_path):
            msgbox.showinfo('step-1 find files from path',
                            'Invalid path={}'.format(self.check_path))
            self.run_state = ''
            return
        finder = FileFinder(self.check_path, self.disp_msg)

        self.time = time.time()
        self.text_result.insert(tk.INSERT, '='*60+'\n')
        with futures.ThreadPoolExecutor() as executor:
            to_do = []
            future_dict = dict()
            future1 = executor.submit(finder.run_subdir_files)
            to_do.append(future1)
            future_dict.update({future1: 1})
            self.text_result.insert(tk.INSERT, 'step-1 process-{} start ... \n'.format(1))

            future2 = executor.submit(self.run_prog1_update_bar, 'step-1')
            to_do.append(future2)
            future_dict.update({future2: 2})
            self.text_result.insert(tk.INSERT, 'bar process-{} start ... \n'.format(2))

            for future in futures.as_completed(to_do):
                if future_dict[future] == 1:
                    result = future.result()
                    finder.find_file_list = result
                    self.text_result.insert(tk.INSERT, 'find files process-{} end\n'.format(future_dict[future]))
                    self.text_result.insert(tk.INSERT, '-'*60+'\n')
                    [self.text_result.insert(tk.INSERT, str(f)+'\n') for i, f in enumerate(result) if i < 5]
                    self.text_result.insert(tk.INSERT, '-'*60+'\n')
                    end_str = 'check files num={}\nelapsed={:.3f}sec\nrunning end\n'.\
                        format(len(finder.find_file_list), time.time() - self.time)
                    self.text_result.insert(tk.INSERT, end_str+'='*60+'\n')
                    self.run_state = ''
        # result
        self.result1 = finder.find_file_list
        self.result_dict[1] = self.task
        self.write_result(task=self.task, step=1, result_seq=self.result1)
        if self.bak_result:
            self.bak_result_fun(1)
        self.text_result.insert(tk.INSERT, 'step-1 end\n{}\n'.format('='*60))

    # run in thread for dispaying progress or running message
    def run_prog1_update_bar(self, name):
        percent = 0
        while True:
            while Signal.pause:
                time.sleep(self.time_step)
            else:
                time.sleep(self.time_step)
            used_time = int(time.time() - self.time)
            percent += .1
            # if percent > 95:
            #     percent = 0
            self.update_progress_bar(percent, used_time, name=name)
            if len(self.run_state) == 0:
                percent = 100
                self.update_progress_bar(percent, used_time, name='finished, elapsed=')
                break

    def run_prog2(self):
        self.result1 = self.read_result(self.task, step=1)
        if self.result1 is None:
            self.disp_msg('no data found! task:{}'.format(self.task))
            self.run_state = ''
            return
        self.text_result.insert(tk.INSERT, '{}\nstep-2 start\n'.format('='*60))
        self.findmd5 = FileMd5(self.result1,
                               msg_fun=self.disp_msg,
                               pbar=self.update_progress_bar)
        self.findmd5.run()
        self.result2 = self.findmd5.find_file_md5_list
        self.result_dict[2] = self.task
        self.write_result(task=self.task,
                          step=2,
                          result_seq=self.result2)
        if self.bak_result:
            self.bak_result_fun(2)
        self.text_result.insert(tk.INSERT, 'step-2 end\n{}\n'.format('='*60))
        self.run_state = ''

    def run_prog3(self):
        self.disp_msg('{}\nstep-3 sart ...\n'.format('='*60))
        self.result2 = self.read_result(task=self.task, step=2)
        if self.result2 is None:
            self.run_state = ''
            return

        self.disp_msg('{}\nstep-3 group by md5 using iter-groupby...\n'.format('='*60))
        findgrp = FileGroupMd5(self.result2,
                               msg_fun=self.disp_msg,
                               pbar=self.update_progress_bar)
        findgrp.run_iter()
        self.result3 = findgrp.find_group_list
        self.result_dict[3] = self.task
        self.write_result(task=self.task,
                          step=3,
                          result_seq=self.result3)
        if self.bak_result:
            self.bak_result_fun(3)
        self.text_result.insert(tk.INSERT, 'step-3 end\n{}\n'.format('='*60))
        self.run_state = ''

    def run_prog4(self):
        # find result4(same files group) from file_group_md5(result3)
        self.result3 = self.read_result(task=self.task, step=3)
        if self.result3 is None:
            self.run_state = ''
            self.disp_msg('read result3 fail!')
            return
        self.disp_msg('{}\nstep-4: check same file group start'.format('='*60))
        findsame = FileGroupSame(self.result3,
                                 msg_fun=self.disp_msg,
                                 pbar=self.update_progress_bar)
        findsame.run()
        self.result4 = findsame.same_group_list
        self.result_dict[4] = self.task
        self.write_result(task=self.task,
                          step=4,
                          result_seq=self.result4)
        self.write_rsult4_report(task=self.task, group_list=self.result4)
        if self.bak_result:
            self.bak_result_fun(4)
        self.disp_msg('step-4 end\n{}\n'.format('='*60))
        self.run_state = ''

    def run_prog5(self):
        self.result4 = self.read_result(task=self.task, step=4)
        if self.result4 is None:
            self.run_state = ''
            return
        self.disp_msg('{}\nstep-5: move file start'.format('='*60))

        # process
        mover = FileMover(
            task=self.task,
            move_group_list=self.result4,
            move_option=self.move_option,
            move_path=self.move_path,
            move_suffix=self.move_suffix,
            move_subdir_num=self.move_subdir_num,
            move_num=self.move_num,
            disp_msg=self.disp_msg,
            pbar=self.update_progress_bar)
        mover.run()

        # result5
        result = mover.result_copy + mover.result_move + mover.result_fail
        self.result_dict[5] = self.task
        self.write_result(self.task, step=5, result_seq=result)
        self.disp_msg('step-5 end\n{}\n'.format('='*60))
        self.run_state = ''

    def run_prog6(self):
        self.disp_msg('5-bakup start\n{}\n'.format('='*60))
        self.bak_result_fun(step=0)
        self.disp_msg('5-bakup end\n{}\n'.format('='*60))
        self.run_state = ''

    def run_getdir(self):
        self._check_path.set('')
        _dir = self.askdiretory()
        self._check_path.set(_dir)

    @staticmethod
    def askdiretory(title='title', init_dir='c:/'):
        # defining options for opening a directory
        dir_opt = options = {}
        options['initialdir'] = init_dir
        options['mustexist'] = False
        options['parent'] = root
        options['title'] = title
        return askdirectory(**dir_opt)

    def update_progress_bar(self, percent=1, used_time=0, name='W00'):
        hour = int(used_time / 3600)
        minute = int(used_time / 60) % 60
        second = used_time % 60
        self.canvas_progress_bar.itemconfig(self.canvas_text, text=name+' %02d:%02d:%02d' % (hour, minute, second))
        self.var_progress_bar_percent.set('%0.2f %%' % percent)
        green_length = int(self.bar_length * percent / 100)
        self.canvas_progress_bar.coords(self.canvas_shape, (0, 0, green_length, 25))

    def disp_msg(self, msg=''):
        if msg[-1] != '\n':
            msg += '\n'
        self.text_result.insert(tk.INSERT, msg)

    @staticmethod
    def write_result(task, step, result_seq):
        filename = task+'_'+'result_'+str(step)+'.csv'
        fp = open(filename, 'w', encoding='utf-8')
        fp.write(str(result_seq))
        fp.close()

    def bak_result_fun(self, step=1):
        step_num = 5
        step_all = 0
        task_path = self.task + '_bak/'
        if not os.path.isdir(task_path):
            os.mkdir(task_path)
        tm = time.localtime()
        frecord = '-'.join(map(str, [tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec]))
        _range = range(1, step_num+1) if step == step_all else [step]

        # bakup [task]_result_step.csv
        for _step in _range:
            step_str = str(_step)
            f_orig = self.task + '_result_' + step_str + '.csv'
            f_dest = task_path + self.task + '_result_' + step_str + '__' + frecord + '.csv'
            if os.path.isfile(f_orig):
                shutil.copyfile(f_orig, f_dest)
                self.disp_msg('done bakup result={}'.format(f_orig))
            else:
                self.disp_msg('fail bakup result={}'.format(f_orig))

        # bakup [task]_result_step_report
        for _step in _range:
            f_orig = self.task + '_result_' + str(_step) + '_report.csv'
            f_dest = task_path + self.task + '_result_' + str(_step) + '_report__' + frecord + '.csv'
            if os.path.isfile(f_orig):
                shutil.copyfile(f_orig, f_dest)
                self.disp_msg('bakup report={}'.format(f_orig))
            else:
                self.disp_msg('fail to bakup report={}'.format(f_orig))

    def write_rsult4_report(self, task, group_list: list):
        check_dir = self.check_path
        save_file = task + '_result_4_' + 'report.csv'
        same_file_group_list = sorted(group_list, key=lambda x: x[0][1])
        total_same_file_num = sum([len(g) for g in same_file_group_list])
        t = time.time()
        tl = time.localtime()
        self.disp_msg('write same files...')
        with open(save_file, 'a', encoding='utf-8') as fw:
            fw.write('='*120 + '\n')
            fw.write('check same files in [{}] at {}\n'.
                     format(check_dir, [tl.tm_year, tl.tm_mon, tl.tm_mday, tl.tm_hour, tl.tm_min, tl.tm_sec])
                     )
            fw.write('total files: {}\n'.format(total_same_file_num))
            fw.write('file groups: {}\n'.format(len(same_file_group_list)))
            fw.write('same groups: {}\n'.format(len([1 for g in same_file_group_list if len(g) > 1])))
            fw.write('-'*120 + '\n')
            for fno, flist in enumerate(same_file_group_list):
                for i, ff in enumerate(flist):
                    if i == 0:
                        fw.write('[{:^5d}]  same number={}  filesize={}\n'.
                                 format(fno, len(flist), ff[1]))
                        fw.write('-' * 50 + '\n')
                    fw.write(' ' * 8 + ff[0] + '\n')
                if fno < len(same_file_group_list) - 1:
                    fw.write('-' * 50 + '\n')
            fw.write('#'*120 + '\n')
        self.disp_msg('write elapsed={:.3f}'.format(time.time()-t))

    def read_result(self, task, step):
        if step not in [1, 2, 3, 4]:
            self.disp_msg('read result only used for step1-4')
            return None
        # use result in memo
        if self.result_dict[step] == task:
            result_list = [self.result1, self.result2, self.result3, self.result4]
            return result_list[step-1]
        # read file
        filename = task+'_'+'result_'+str(step)+'.csv'
        if os.path.isfile(filename):
            self.disp_msg('reading {}_result_{}'.format(task, step))
            fp = open(filename, 'r', encoding='utf-8')
            result = fp.read()
            fp.close()
            result = eval(result)
            return result
        else:
            msgbox.showwarning('Step-' + str(step) + ' Warning',
                               'result file not found: {}'.format(filename))
            return None


class FileMover:
    def __init__(self,
                 task,
                 move_group_list,
                 move_option,
                 move_path,
                 move_suffix,
                 disp_msg,
                 pbar,
                 move_subdir_num=1000,
                 move_num=-1,
                 ):
        self.task = task
        self.group_list = move_group_list
        self.option = move_option[0]
        self.path = move_path
        self.suffix = move_suffix
        self.disp_msg = disp_msg
        self.pbar = pbar
        self.move_num = move_num
        self.dir_file_num = move_subdir_num

        # init main_path
        self.move_list = []
        self.move_suffix_dict = dict()
        self.move_list = self.file_filter(self.group_list, self.suffix)
        self.move_suffix_dict = self.get_suffix_dict(self.move_list)

        # cut group_list
        if 0 < self.move_num < len(self.group_list):
            self.group_list = self.group_list[:self.move_num]
        disp_msg('groups to move number={}'.format(len(self.group_list)))

        # set result
        self.result_fail = []
        self.result_copy = []
        self.result_move = []

    def run(self):
        move_list = self.move_list
        # copy
        if self.option in ['1']:
            self.copy_delete(move_list, target_path=self.path, move=False)
        # delete
        elif self.option in ['2']:
            self.copy_delete(move_list, target_path=self.path, move=True)
        else:
            self.delete(move_list)
        # disp result
        self.disp_msg('copy files={}'.format(len(self.result_copy)))
        self.disp_msg('move files={}'.format(len(self.result_move)))
        self.disp_msg('fail files={}'.format(len(self.result_fail)))
        # write result
        self.result_report()

    def copy_delete(self, move_list, target_path, move=False):
        move_count_dict = {k: 0 for k in self.move_suffix_dict}
        max_file_num = self.dir_file_num

        self.mkdir(target_path)

        # copy proc
        move_fail = []
        t = time.time()
        tlen = len(move_list)
        moved_set = set()
        for fi, fg in enumerate(move_list):
            self.pbar(percent=int(fi/tlen*100), used_time=time.time()-t, name='copy')

            # the longest name file in a group
            if len(fg) > 1:
                _fg = sorted(fg, key=lambda x: len(os.path.split(x)[1]))
                f_copy = _fg[-1]
                _fg = _fg[:-1]      # else files
            elif len(fg) == 1:
                f_copy = fg[0]
                _fg = []
            else:   # empty fg
                continue

            if os.path.isfile(f_copy):
                # define subdir
                f_suffix = FileMover.get_suffix(f_copy)
                dir_no = move_count_dict[f_suffix] // max_file_num
                subdir = f_suffix if len(f_suffix) > 0 else '__null__'
                subdir = subdir if dir_no == 0 else subdir + '_' + str(dir_no)
                target_path_ = target_path + '/' + subdir
                self.mkdir(target_path_)

                # split to path, filename
                filename = os.path.split(f_copy)[1]
                filename_suffix = self.get_suffix(filename)
                filename_prefix = self.get_prefix(filename)
                # disp 6 items at most
                do_str = 'move: ' if move else 'copy: '
                if (fi < 2) or (fi > tlen - 2):
                    self.disp_msg(do_str + f_copy + ' ==> ' + target_path + '/' + filename)
                elif fi == 4:
                    self.disp_msg('   ......')

                target_file = target_path_ + '/' + filename
                try:
                    # move:: copy and delete
                    if move:
                        # move longest
                        addi = 1
                        while target_file in moved_set:
                            # self.disp_msg('repeat move: '+filename)
                            target_file = target_path_ + '/' + \
                                          filename_prefix + '___' + str(addi) + '.' + filename_suffix
                            addi += 1
                        shutil.move(f_copy, target_file)
                        move_count_dict[f_suffix] += 1
                        moved_set.add(target_file)
                        self.result_move.append('copy done:'+f_copy+' ==> '+target_file)
                        # remove else
                        for f in _fg:
                            if os.path.isfile(f):
                                os.remove(f)
                                self.result_move.append('dele done:' + f)
                            else:
                                self.result_fail.append('miss file:' + f_copy)
                    # copy only
                    else:
                        addi = 1
                        while target_file in moved_set:
                            # self.disp_msg('repeat move: '+filename)
                            target_file = target_path_ + '/' + \
                                          filename_prefix + '___' + str(addi) + '.' + filename_suffix
                            addi += 1
                        shutil.copyfile(f_copy, target_file)
                        move_count_dict[f_suffix] += 1
                        moved_set.add(target_file)
                        self.result_copy.append('copy done: '+f_copy+' ==> '+target_file)
                except IOError:
                    move_fail.append('move or copy error: ' + f_copy)
            else:
                self.result_fail.append('miss file:'+f_copy)
        for k in move_count_dict:
            self.disp_msg('  suffix:{} -- {}'.format(k, move_count_dict[k]))
        self.disp_msg('copy end, elapsed={}'.format(time.time()-t))

    def delete(self, move_list):
        # longest name file reserved
        t = time.time()
        tlen = len(move_list)
        self.disp_msg('delete groups={} ...'.format(len(move_list)))
        for fi, fg in enumerate(move_list):
            self.pbar(percent=int(fi/tlen*100), used_time=time.time()-t, name='delete')
            # preserve long filename
            _fg = sorted(fg, key=lambda x: len(os.path.split(x)[1]))
            _fg = _fg[:-1]
            for _f in _fg:
                # delete
                if os.path.isfile(_f):
                    try:
                        os.remove(_f)
                        self.result_move.append('dele done: ' + _f)
                    except OSError:
                        self.result_fail.append('dele fail: ' + _f)
                else:
                    self.result_fail.append('miss file: ' + _f)
        self.disp_msg('deleted files={}, delete fail={}, elapsed={}'.
                      format(len(self.result_move), len(self.result_fail), time.time()-t))
        return

    def result_report(self):
        result_file = self.task+'_result_5_report.csv'
        f_old = True if os.path.isfile(result_file) else False
        with open(result_file, 'a', encoding='utf-8') as fp:
            if f_old:
                fp.write('-'*60+'\n')
            for s in self.result_copy:
                fp.write(s+'\n')
            for s in self.result_move:
                fp.write(s+'\n')
            for s in self.result_fail:
                fp.write(s+'\n')

    @staticmethod
    def mkdir(path):
        if not os.path.isdir(path):
            try:
                os.mkdir(path)
            except OSError:
                msgbox.showwarning('move files error', 'invalid path name: {}'.format(path))
                return False
        return True

    @staticmethod
    def file_filter(group_list, suffix):
        # suffix filter
        _suf = suffix.replace('.', '').upper()
        if _suf == '*'*len(_suf):
            move_list = [[f[0] for f in fg] for fg in group_list]
        elif '*' in _suf:
            pt = _suf.replace('*', '.*')
            move_list = [[f[0] for f in fg
                          if re.match(pt, f[0][f[0].rfind('.')+1:].upper())]
                         for fg in group_list]
            return move_list
        else:
            move_list = [[f[0] for f in fg
                          if f[0][f[0].rfind('.')+1:].upper() == _suf]
                         for fg in group_list]
        move_list = [f for f in move_list if len(f) > 0]
        return move_list

    @staticmethod
    def get_suffix_dict(move_list):
        move_suffix_dict = {}   # suffix: files_count
        for fg in move_list:
            for f in fg:
                f_name_suffix = FileMover.get_suffix(f)
                if f_name_suffix in move_suffix_dict:
                    move_suffix_dict[f_name_suffix] += 1
                else:
                    move_suffix_dict[f_name_suffix] = 1
        return move_suffix_dict

    @staticmethod
    def get_suffix_old(file_name=''):
        f_name_split = os.path.split(file_name)[1].split('.')
        if len(f_name_split) == 2:
            return f_name_split[1]
        else:
            return ''

    @staticmethod
    def get_suffix(file_name=''):
        f_name = os.path.split(file_name)[1]
        if '.' in f_name:
            return f_name.split('.')[-1]
        return ''

    @staticmethod
    def get_prefix(filename=''):
        filename_suffix = FileMover.get_suffix(filename)
        return filename if len(filename) == '' else filename[0: filename.find(filename_suffix)-1]


class FileFinder:
    def __init__(self, path='', disp_fun=None):
        self.root_dir = path
        self.disp_fun = disp_fun

        # result data
        self.find_dir_list = []
        self.find_file_list = []
        self.find_fail = []

        # result number
        self.total_size = 0
        self.total_file_num = 0
        self.find_dir_num = 0

    def run_subdir_files(self):
        self.total_size = 0
        self.total_file_num = 0
        self.find_dir_list = []
        self.find_file_list = []
        self.find_dir_num = 0
        self.get_subdir_files(self.root_dir)
        return self.find_file_list

    def get_subdir_files(self, check_dir):
        # if pause button pressed
        if Signal.pause:
            disp = False
            while True:
                if not disp:
                    self.disp_fun('system paused ...')
                    disp = True
                time.sleep(0.1)
                if not Signal.pause:
                    self.disp_fun('system resumed ...')
                    break

        # start check current dir
        self.find_dir_num += 1
        this_size = 0
        # get files and dirs of current dir
        try:
            this_check = os.listdir(check_dir)
        except IOError:
            self.find_fail.append('dir: '+check_dir)
            return
        # add files and seek dir recursively
        for d in this_check:
            full_name = check_dir + '/' + d
            if os.path.isdir(full_name):
                self.get_subdir_files(full_name)
            else:
                this_size += os.path.getsize(full_name)
                self.find_file_list.append(full_name)
                self.total_file_num += 1
        self.find_dir_list.append([check_dir, this_size])
        self.total_size += this_size
        return


class FileMd5:
    def __init__(self, file_list=None, msg_fun=None, pbar=None):
        self.msg_fun = msg_fun
        self.pbar = pbar
        self.find_file_list = file_list

        # result data
        self.find_file_md5_list = []
        self.find_fail = []

        # result number
        self.total_size = 0
        self.total_file_num = 0
        self.total_group_num = 0

    def run(self):
        if len(self.find_file_list) == 0:
            self.msg_fun('no file in file_list!')
            return
        else:
            self.msg_fun('-'*60+'\n')
        self.find_file_md5_list = self.run_md5_thread(self.find_file_list)
        self.find_file_md5_list = sorted(self.find_file_md5_list, key=lambda x: x[2])

    def run_md5_thread(self, find_file_list):
        t = time.time()
        seg_num = os.cpu_count()
        self.total_file_num = len(find_file_list)
        seg_len = int(self.total_file_num/seg_num)
        file_seg_list = [find_file_list[i*seg_len:(i+1)*seg_len] if i < seg_num-1 else
                         find_file_list[i*seg_len:]
                         for i in range(seg_num)]
        with futures.ThreadPoolExecutor(max_workers=seg_num) as executor:
            to_do = []
            future_dict = dict()
            for fi, file_list in enumerate(file_seg_list):
                future = executor.submit(self.run_md5, file_list, 'md5 thread-'+str(fi))
                to_do.append(future)
                future_dict.update({future: fi})
            self.msg_fun('-'*60+'\n')
            result = []
            for future in futures.as_completed(to_do):
                res = future.result()
                result.extend(res)
                # self.msg_fun('md5 thread-{} end ... \n'.format(fi))
        self.msg_fun('{1}\ncalc md5 end  elapsed={0:.2f} \n'.format(time.time()-t, '-'*60))
        return result

    def run_md5(self, file_list=None, task='run md5'):
        t = time.time()
        self.msg_fun('{}\n'.format(task))
        if len(file_list) == 0:
            self.msg_fun('no file found!\n')
            return

        find_file_md5_list = []
        total = len(file_list)
        for i, f in enumerate(file_list):
            # if pause button pressed
            if Signal.pause:
                disp = False
                while True:
                    if not disp:
                        self.msg_fun('md5 paused ...')
                        disp = True
                    time.sleep(0.1)
                    if not Signal.pause:
                        self.msg_fun('md5 resumed ...')
                        break
            # calc md5 thread
            percent = int(i/total*100)
            self.pbar(percent, time.time()-t, task)
            try:
                f_m5 = self.make_md5(f)
            except IOError:
                self.find_fail.append('file: ' + f)
                continue
            find_file_md5_list.append([f, os.path.getsize(f), f_m5])
        self.msg_fun(task+'end  elapsed: {:3f}\n'.format(time.time()-t))
        return find_file_md5_list

    @staticmethod
    def make_md5(filename):
        # block_size = 10*1024 * 1024
        m5 = hashlib.md5()
        with open(filename, 'rb') as fp:
            m5.update(fp.read())
            # while True:
            #     data = fp.read(block_size)
            #     if not data:
            #         break
            #     m5.update(data)
        return m5.digest()


class FileGroupMd5:
    def __init__(self, md5_list=None, msg_fun=None, pbar=None):
        self.msg_fun = msg_fun
        self.pbar = pbar
        self.md5_list = md5_list

        # result data
        self.find_group_list = []
        self.find_fail = []

    # deprecated 2020.5.25
    def run_deprecated(self):
        tstart = time.time()
        self.msg_fun('get md5 group ...')

        # create groups with same files
        file_size_md5_list = self.md5_list
        file_group_same_md5 = []
        current_group = []
        last_md5 = b''
        total_len = len(file_size_md5_list)
        for fi, f_size_md5 in enumerate(file_size_md5_list):
            # if pause button pressed
            if Signal.pause:
                disp = False
                while True:
                    if not disp:
                        self.msg_fun('group paused ...')
                        disp = True
                    time.sleep(0.1)
                    if not Signal.pause:
                        self.msg_fun('group resumed ...')
                        break
            self.pbar(int(fi/total_len*100), time.time()-tstart, 'group by md5 ')
            if f_size_md5[2] != last_md5:
                # add group to file_group if 2 more files in
                # if len(current_group) > 1:
                # new change: including all groups
                file_group_same_md5.append(current_group)
                # new group
                current_group = [f_size_md5]  # [ftuple(f_name_md5[0], os.path.getsize(f_name_md5[0]), f_name_md5[2])]
                last_md5 = f_size_md5[2]
            else:
                # current_group.append(ftuple(f_name_md5[0], os.path.getsize(f_name_md5[0]), f_name_md5[2]))
                current_group.append(f_size_md5)
        # add group not empty    #with 2 more files
        if len(current_group) > 0:
            file_group_same_md5.append(current_group)

        self.msg_fun('md5 group end,  elapsed={:.3f}\n'.format(time.time()-tstart))
        self.find_group_list = file_group_same_md5

    def run_iter(self):
        import itertools as itl
        tstart = time.time()
        self.msg_fun('get md5 group ...')
        # create groups with same files
        # file_size_md5_list = sorted(self.md5_list, key=lambda x: x[2])
        file_size_md5_list = self.md5_list  # sorting finished in FileMd5.run
        group = itl.groupby(file_size_md5_list, key=lambda x: x[2])
        result_group = []
        for k, fg in group:
            file_list = list(fg)
            # change: including all groups
            # if len(file_list) > 1:
            result_group.append(file_list)
        self.msg_fun('get md5 group end, elapsed={}'.format(time.time()-tstart))
        self.find_group_list = result_group


class FileGroupSame:
    def __init__(self, md5_group_list=None, msg_fun=None, pbar=None):
        self.msg_fun = msg_fun
        self.pbar = pbar
        self.md5_group_list = md5_group_list

        # result data
        self.same_group_list = []
        self.find_fail = []

    def run(self):
        tstart = time.time()
        self.msg_fun('get same content group start ...')
        workers = os.cpu_count()
        seg_len = int(len(self.md5_group_list)/workers)
        file_seg_list = [self.md5_group_list[i*seg_len:(i+1)*seg_len]
                         if i < workers-1 else
                         self.md5_group_list[i*seg_len:]
                         for i in range(workers)]
        with futures.ThreadPoolExecutor(max_workers=workers) as executor:
            to_do = []
            future_dict = dict()
            for fi, fglist in enumerate(file_seg_list):
                future = executor.submit(self.run_get_groups_same_content, fglist)
                to_do.append(future)
                future_dict.update({future: fi})
                self.msg_fun('check same group thread-{} start ...'.format(fi))
            result = []
            for future in futures.as_completed(to_do):
                res = future.result()
                result.extend(res)
                self.msg_fun('thread-{} end'.format(future_dict[future]))
        self.msg_fun('check same content total elapsed: {:.2f}'.format(time.time()-tstart))
        self.same_group_list = result
        # return result

    def run_get_groups_same_content(self, file_group_same_md5):
        t = time.time()
        file_group_same_content = []
        tlen = len(file_group_same_md5)
        for i, fg in enumerate(file_group_same_md5):
            # if pause button pressed
            if Signal.pause:
                disp = False
                while True:
                    if not disp:
                        self.msg_fun('group paused ...')
                        disp = True
                    time.sleep(0.1)
                    if not Signal.pause:
                        self.msg_fun('group resumed ...')
                        break
            self.pbar(int(i/tlen*100), time.time()-t, '')
            if len(fg) == 1:
                file_group_same_content.append(fg)
                continue
            elif len(fg) == 2:
                if fg[0][2] == fg[1][2]:
                    # size == 0
                    if fg[0][1] == 0:
                        file_group_same_content.append(fg)
                        continue
                    try:
                        with open(fg[0][0], 'rb') as f1, open(fg[1][0], 'rb') as f2:
                            f1r = f1.read()
                            f2r = f2.read()
                            if f1r == f2r:
                                file_group_same_content.append(fg)
                            else:
                                file_group_same_content.extend([[g] for g in fg])
                    except IOError:
                        err = 'error read: {} or {}'.format(fg[0][0], fg[1][0])
                        self.find_fail.append(err)
                        self.msg_fun(err)
                continue
            # len(fg) > 2
            else:
                same_group = []
                same_to_last = False
                for fi, f in enumerate(fg[:-1]):
                    # compare by md5
                    if f[2] == fg[fi+1][2]:
                        if same_to_last:
                            same_group.append(f)
                        else:
                            if len(same_group) > 0:
                                file_group_same_content.append(same_group)
                            same_group = [f]
                        same_to_last = True
                        # last item
                        if fi == len(fg) - 2:
                            same_group.append(fg[fi+1])
                            file_group_same_content.append(same_group)
                    else:
                        if same_to_last:
                            same_group.append(f)
                            file_group_same_content.append(same_group)
                            same_group = []
                        else:
                            # single file, different from last and next
                            # save and clear same_group
                            # new change: add single file
                            if len(same_group) > 0:
                                file_group_same_content.append(same_group)
                                same_group = []
                        same_to_last = False
        return file_group_same_content


class Tasker:
    template = \
        """
        [0]
        name = task_xxx                     # 任务名称，用于生成结果: logname_model_year_month_day.log
        path = d:/test                      # 是否显示计算过程信息 message to consol
        bakresult = 1-do not bakup          # 运行时的备份选择
        move_path = task_move               # 目标目录
        move_suffix = csv                   # 移动文件后缀
        move_option = 1-copy longest        # 移动选择
        move_num = -1                       # 移动文件数, -1:不限制数量
        move_subdir_num = 1000              # 目标目录子目录文件最大数
        recent = False                      # 最近使用
        """
    task_definition = {
        'path': 'task_xxx',
        'move_path': 'd:/test',
        'move_suffix': 'csv',
        'bakresult': ('1-do not bakup', 'bakup'),
        'move_option': ('1-copy longest', '2-copy and del', 'del but logest'),
        'move_num': '-1',
        'move_subdir_num': '1000',
        'recent': False
    }

    def __init__(self, task_file=MainWin.config_file, disp_fun=None):
        self.disp_fun = disp_fun
        self.task_file = task_file
        self.task_dict = []
        self.task_recent = None
        if not os.path.isfile(self.task_file):
            self.new_file(self.task_file)
        self.load(self.task_file)
        self.task_recent = self.get_recent()

    def load(self, task_file=''):
        if os.path.isfile(task_file):
            self.task_dict = []
            cfper = cfg.ConfigParser()
            cfper.read(task_file, encoding='utf-8')
            for name in cfper.keys():
                if name == 'DEFAULT':
                    continue
                task_dict = dict()
                for item in cfper[name].keys():
                    v = cfper[name][item]
                    v = v[0: v.find('#')] if '#' in v else v
                    if item in ['move_subdir_num', 'recent', 'move_num']:
                        task_dict[item] = eval(v)
                    else:
                        task_dict[item] = v.strip()
                self.task_dict.append(task_dict)

    def save(self, task_file=''):
        with open(task_file, 'w', encoding='utf-8') as fp:
            for ti, task in enumerate(self.task_dict):
                fp.write('[task-' + str(ti) + ']\n')
                for item in task.keys():
                    fp.write(item + '=' + str(task[item]) + '\n')

    def new_file(self, task_file):
        with open(task_file, 'a', encoding='utf-8') as fp:
            ms = self.template.strip().split('\n')
            for ss in ms:
                fp.write(ss.strip() + '\n')

    def add_task(self, task):
        ch = self.check_task(task)
        if ch == 0:
            self.disp_fun('invalid task add!')
            return False
        elif ch == 1:
            for ti, t in enumerate(self.task_dict):
                if t['name'] == task['name']:
                    for k in task.keys():
                        t[k] = task[k]
                    self.task_dict[ti] = t
                    break
        else:
            self.task_dict.append(task)

    def check_task(self, task=None):
        if not isinstance(task, dict):
            return 0
        if task['name'] in [t['name'] for t in self.task_dict]:
            return 1
        return 2

    def get_task(self, name=None):
        for t in self.task_dict:
            if t['name'] == name:
                return t
        else:
            return None

    def get_recent(self):
        for task in self.task_dict:
            if task['recent']:
                return task
        if len(self.task_dict) > 0:
            self.task_dict[0]['recent'] = True
            return self.task_dict[0]
        else:
            return self.get_default()

    def set_recent(self, name=''):
        _set = False
        for i, t in enumerate(self.task_dict):
            if t['name'] == name:
                self.task_dict[i]['recent'] = True
                _set = True
            else:
                self.task_dict[i]['recent'] = False
        if not _set:
            if len(self.task_dict) > 0:
                self.task_dict[0]['recent'] = True
            else:
                self.task_dict.append(self.get_default())
                self.task_dict[0]['recent'] = True

    def get_default(self):
        task = dict()
        for item in self.task_definition:
            if isinstance(self.task_definition[item], tuple):
                task[item] = self.task_definition[item][0]
            else:
                task[item] = self.task_definition[item]
        return task


class Document:
    help_doc = \
        """
        【功能说明】

        本软件用于检测一个目录中的重复文件, 设有运行功能（1-5）及备份功能bakup
        
        1.find   [step-1]: 获取路径文件名

        2.md5    [step-2]: 计算每个文件内容md5值

        3.group  [step-3]: 按文件的md5值进行分组

        4.result [step-4]: 对分组内文件进行内容比较，产生相同内容的文件的分组

        5.move   [step-5]: 移除重复文件（1-只拷贝重复文件到目标目录，使用最长文件名作为目标文件名
                                      
                                         2-拷贝重复文件到目标目录，并删除原目录中文件，保留原目录中最长文件名文件
                                      
                                         3-只删除原目录中重复文件，保留原目录中最长文件名文件）

        6.bakup  [step-6]: 备份前面5个步骤的运行结果
        
        运行功能1-5需要依次运行，每一步骤读取上一步的结果，并单独保存本步骤的运行结果[task]_result_[step].csv.
        
        如果选择了bak_result/2-bakup result, 则每个步骤都在运行后备份结果及4-5步骤的报告report。
        
        【参数说明】
        
        task: 任务名称。根据情况设定用以表示各类输出结果，不能包含非文件名、路径名合法字符
        
        path: 检查目录名。需要带有完整路径名称。在该目录中检查内容相同的文件。
        
        bakresult: 备份结果选择。是否在运行后保存运行结果（[task]_result_[step].csv)及报告（..._report.csv）
        
        move_option: 移除文件选择。根据选择将检查目录的文件移动到目标目录 move_path
        
        move_path: 目标目录名。将相同内容的文件移除到的目标目录，子目录是文件的后缀名（相同后缀名的文件被移动到相同目录）
        
        move_suffix: 移除文件后缀名。只处理该后缀名的文件，支持使用‘*’匹配，如，‘*ab'指末尾为ab的后缀，’*'为所有名称
        
        move_subdir_num: 目标目录子目录文件数。超出该数字则文件移动到递增子目录 suffix_1,_2,..

        【输出结果】
        
        步骤结果： [task]_result_[step].csv，step = 1,2,3,4,5
        
        步骤报告： [task]_result_[[step]_report.csv, step = 4,5

        备份数据： 将结果和报告备份到目录[task]_bak/中，备份后的文件名为
                   [task]_result_[step]_[year-mon-mday-hour-min-sec].csv
                   [task]_result_[step]_report_[year-mon-mday-hour-min-sec].csv
        
        """


if __name__ == '__main__':
    root = Tk()
    w = MainWin(root)
    root.mainloop()
